-- Author: chleniang@163.com

local _M = {}

---content-type通过parse_content_type()解析后是 application/x-www-form-urlencoded
_M.CONTENT_TYPE_WWW_FORM_URLENCODED = 1

---content-type通过parse_content_type()解析后是 multipart/form-data
_M.CONTENT_TYPE_FORM_DATA = 2

---content-type通过parse_content_type()解析后是 application/json
_M.CONTENT_TYPE_JSON = 3

---content-type通过parse_content_type()解析后是非以上两种;
---如: text/html text/plain text/xml 等...
_M.CONTENT_TYPE_OTHER = 9

---form-data内容空行
_M.FORM_DATA_LINE_EMPTY = 0

---form-data内容项的开始行 "--" + boundary字符串
_M.FORM_DATA_LINE_TYPE_BOUNDARY_START = 1

---form-data内容的结束行 "--" + boundary字符串 + "--"
_M.FORM_DATA_LINE_TYPE_BOUNDARY_END = 2

---Content-Disposition行且包含 name="参数名" 没有 filename
_M.FORM_DATA_LINE_TYPE_FIELD = 3

---Content-Disposition行且包含 filename="文件名"
_M.FORM_DATA_LINE_TYPE_FILE = 4

---解析content-type字符串,返回具体类型(常量值)
---@param content_type_str string content-type原始字符串
---@return integer
local parse_content_type = function(content_type_str)
    local res

    -- application/x-www-form-urlencoded; charset=utf-8
    res = ngx.re.find(content_type_str,
                      "^\\s*application\\s*/\\s*x-www-form-urlencoded\\s*(;$|$|(;\\s*[^;]+))",
                      "ijos")
    if res then
        return _M.CONTENT_TYPE_WWW_FORM_URLENCODED
    end

    -- multipart/form-data; boundary=------84146920
    res = ngx.re.find(content_type_str,
                      "^\\s*multipart\\s*/\\s*form-data\\s*;\\s*boundary=((\"([^\"]+)\")|([^\",;\\s]+))",
                      "ijos")
    if res then
        return _M.CONTENT_TYPE_FORM_DATA
    end

    -- application/json; charset=utf-8
    res = ngx.re.find(content_type_str,
                      "^\\s*application\\s*/\\s*json(;$|$|(;\\s*[^;]+))",
                      "ijos")
    if res then
        return _M.CONTENT_TYPE_JSON
    end

    return _M.CONTENT_TYPE_OTHER
end
_M.parse_content_type = parse_content_type

---根据Content-Type字符串解析出 boundary字符串;
---<br>     如Content-Type: "multipart/form-data; boundary=-------864346563"
---<br>     返回 "-------864346563"
---@param content_type_str string
---@return string|nil
local parse_form_data_boundary = function(content_type_str)
    local m = ngx.re.match(content_type_str,
                           ";\\s*boundary=((\"([^\"]+)\")|([^\",;\\s]+))",
                           "ijos")
    if not m then
        return nil
    end
    return (m[3] or m[4] or nil)
end
_M.parse_form_data_boundary = parse_form_data_boundary

---验证指定form-data内容是否是boundary行;
---<br>     如果content是 "--" + boundary字符串 开头 则返回true;
---@param boundary_str string boundary字符串(通过Content-Type获取)
---@param content string 待检测字符串
---@return boolean
local is_form_data_boundary_line = function(boundary_str, content)
    local f = string.find(content, "--" .. boundary_str, 1, true)
    if f then
        return true
    end
    return false
end
_M.is_form_data_boundary_line = is_form_data_boundary_line


---获取form-data某行字符串的类型及附加信息;
---<br>     如果content是 "--" + boundary字符串 则是form-data某一项数据开始行;
---<br>     如果content是 "--" + boundary字符串 + "--"  则是form-data内容结束行;
---<br>     如果content是 空白行  则是form-data内容分隔行;
---<br>     如果content是 "Content-Disposition:form-data; filename=" 则是上传文件信息行,同时返回附加信息
---<br>     如果content是 "Content-Disposition:form-data; name="(不含 filename) 则是普通变量信息行,同时返回附加信息
---<br>     如果content非以上情况返回nil;
---@param boundary_str string boundary字符串(通过Content-Type获取)
---@param content string 待检测字符串
---@return integer | nil
---@return nil | table
local parse_form_data_line = function(boundary_str, content)
    if content == "" then
        return _M.FORM_DATA_LINE_EMPTY
    end
    if "--" .. boundary_str == content then
        return _M.FORM_DATA_LINE_TYPE_BOUNDARY_START
    end
    if "--" .. boundary_str .. "--" == content then
        return _M.FORM_DATA_BOUNDARY_TYPE_END
    end

    local filename_pattern = "^\\s*Content-Disposition:\\s*form-data\\s*;(.+)[;|\\s+]filename=\"([^\"]*)\".*"
    local name_pattern = "^\\s*Content-Disposition:\\s*form-data\\s*.*[;|\\s+]name=\"([^\"]*)\".*"
    local m, err = ngx.re.match(content, filename_pattern, "ijo")
    if m then
        -- 解析到 filename
        local addition = {
            filename = m[2]
        }

        m = ngx.re.match(content, name_pattern, "ijo")
        if m then
            -- 解析到 name
            addition["name"] = m[1]
        elseif err then
            ngx.log(ngx.ERR, "[easy_waf] ngx.re.match() form-data 'filename > name' ERR:" .. err)
        end

        return _M.FORM_DATA_LINE_TYPE_FILE, addition
    elseif err then
        ngx.log(ngx.ERR, "[easy_waf] ngx.re.match() form-data 'filename' ERR:" .. err)
    end

    m, err = ngx.re.match(content, name_pattern, "ijo")
    if m then
        -- 解析到 name
        return _M.FORM_DATA_LINE_TYPE_FIELD, { name = m[1] }
    elseif err then
        ngx.log(ngx.ERR, "[easy_waf] ngx.re.match() form-data 'name' ERR:" .. err)
    end
    return nil
end
_M.parse_form_data_line = parse_form_data_line

return _M
